/**
 * Copyright (C) 2008 by The Regents of the University of California
 * Redistribution of this file is permitted under the terms of the GNU
 * Public License (GPL).
 *
 * @author Junghoo "John" Cho <cho AT cs.ucla.edu>
 * @date 3/24/2008
 */

#include <cstdio>
#include <iostream>
#include <fstream>
#include <vector>
#include "Bruinbase.h"
#include "SqlEngine.h"
#include "BTreeIndex.h"
#include "BTreeNode.h"

using namespace std;

// external functions and variables for load file and sql command parsing 
extern FILE* sqlin;
int sqlparse(void);


RC SqlEngine::run(FILE* commandline)
{
  fprintf(stdout, "Bruinbase> ");

  // set the command line input and start parsing user input
  sqlin = commandline;
  sqlparse();  // sqlparse() is defined in SqlParser.tab.c generated from
               // SqlParser.y by bison (bison is GNU equivalent of yacc)

  return 0;
}

RC SqlEngine::select(int attr, const string& table, const vector<SelCond>& cond)
{
  RecordFile rf;   // RecordFile containing the table
  RecordId   rid;  // record cursor for table scanning

  RC     rc;
  int    key;     
  string value;
  int    count = 0;
  int    diff;


  int indexExists = 1;
  BTreeIndex idx = BTreeIndex();
  // open the index file


  if ((rc = idx.open(table + ".idx", 'r')) < 0) {
    indexExists = 0;
  }

  // open the table file
  if ((rc = rf.open(table + ".tbl", 'r')) < 0) {
    fprintf(stderr, "Error: table %s does not exist\n", table.c_str());
    return rc;
  }


  int useIndex = 0;
  vector<SelCond> keyCond;
  vector<SelCond> valCond;

  for (unsigned i = 0; i < cond.size(); i++) {
    if (cond[i].attr == 1) {
      if(cond[i].comp != SelCond::NE)
        useIndex = 1;
      keyCond.push_back(cond[i]);
    } else {
      valCond.push_back(cond[i]);
    }
  }

  if (indexExists && useIndex) {


    int min;
    int max;
    int eq;
    bool minb = 0;
    bool maxb = 0;
    bool eqb=0;

    vector<int> neq;
    for (unsigned i = 0; i < keyCond.size(); i++) {
      switch (keyCond[i].comp) {
        case SelCond::EQ:
          if ((eqb && eq != atoi(keyCond[i].value)) || 
              find(neq.begin(), neq.end(),atoi(keyCond[i].value)) != neq.end()) {
            goto exit_select;
          }
          eq = atoi(keyCond[i].value);
          eqb = 1;
          break;
        case SelCond::NE:
          if(eqb && eq == atoi(keyCond[i].value)) {
            goto exit_select;
          }
          neq.push_back(atoi(keyCond[i].value));
          break;
        case SelCond::GT:
          if (!minb || minb && atoi(keyCond[i].value) + 1 > min) {
            min = atoi(keyCond[i].value) + 1;
            minb = 1;
          }
          break;
        case SelCond::LT:
          if (!maxb || maxb && atoi(keyCond[i].value) - 1 < max) {
            max = atoi(keyCond[i].value) - 1;
            maxb = 1;
          }
          break;
        case SelCond::GE:
          if (!minb || minb && atoi(keyCond[i].value) > min) {
            min = atoi(keyCond[i].value);
            minb = 1;
          }
          break;
        case SelCond::LE:
          if (!maxb || maxb && atoi(keyCond[i].value)  < max) {
            max = atoi(keyCond[i].value);
            maxb = 1;
          }
          break;
      }
    }

    if (maxb && !minb) {
      if (eqb && eq > max){
        goto exit_select;
      }
    } else if ( minb && ! maxb) {
      if (eqb && eq < min) {
        goto exit_select;
      }
    } else if (minb && maxb) {
      if(min > max || (eqb && (eq < min || eq > max))){
          goto exit_select;
      }
    } else if (eqb || neq.size() > 0){

    } else {
      goto exit_select;
    }

    if (eqb) {
      //locate, run value checks
      IndexCursor cur;
      int retval = idx.locate(eq, cur);

      if (retval == 0) {

        idx.readForward(cur, key, rid);
        if (key == eq) {
          count++;
          if(valCond.size() > 0 || attr == 2 || attr == 3 )
          {
             if ((rc = rf.read(rid, key, value)) < 0) {
              fprintf(stderr, "Error: while reading a tuple from table %s\n", table.c_str());
              goto exit_select;
            }
          }

          for (unsigned i = 0; i < valCond.size(); i++) {
            // compute the difference between the tuple value and the condition value
            diff = strcmp(value.c_str(), valCond[i].value);

            // skip the tuple if any condition is not met
            switch (valCond[i].comp) {
              case SelCond::EQ:
                if (diff != 0) goto exit_select;
                break;
              case SelCond::NE:
                if (diff == 0) goto exit_select;
                break;
              case SelCond::GT:
                if (diff <= 0) goto exit_select;
                break;
              case SelCond::LT:
                if (diff >= 0) goto exit_select;
                break;
              case SelCond::GE:
                if (diff < 0) goto exit_select;
                break;
              case SelCond::LE:
                if (diff > 0) goto exit_select;
                break;
            }
          }

          switch (attr) {
            case 1:  // SELECT key
              fprintf(stdout, "%d\n", key);
              break;
            case 2:  // SELECT value
              fprintf(stdout, "%s\n", value.c_str());
              break;
            case 3:  // SELECT *
              fprintf(stdout, "%d '%s'\n", key, value.c_str());
              break;
          }

        } else {
          goto exit_select;
        }
      }


    } else {


      if (!minb) {
        min = idx.minkey;
      }

      //locate, run value checks
      IndexCursor cur;
      int retval = idx.locate(min, cur);
      if (retval == 0) {

         while(idx.readForward(cur,key,rid) == 0) {
            bool doPrint = true;
            if (!maxb || key <= max) {
              
              if(valCond.size() > 0 || attr == 2 || attr == 3 ) {
                 if ((rc = rf.read(rid, key, value)) < 0) {
                  fprintf(stderr, "Error: while reading a tuple from table %s\n", table.c_str());
                  goto exit_select;
                }
              }

              for (unsigned i = 0; i < valCond.size(); i++) {
                // compute the difference between the tuple value and the condition value
                diff = strcmp(value.c_str(), valCond[i].value);

                // skip the tuple if any condition is not met
                switch (valCond[i].comp) {
                  case SelCond::EQ:
                    if (diff != 0)
                      doPrint = false;
                    break;
                  case SelCond::NE:
                    if (diff == 0)
                      doPrint = false;
                    break;
                  case SelCond::GT:
                    if (diff <= 0)
                      doPrint = false;
                    break;
                  case SelCond::LT:
                    if (diff >= 0)
                      doPrint = false;
                    break;
                  case SelCond::GE:
                    if (diff < 0)
                      doPrint = false;
                    break;
                  case SelCond::LE:
                    if (diff > 0)
                      doPrint = false;
                    break;
                }
              }

              if (find(neq.begin(), neq.end(),key) != neq.end()) {
                doPrint = false;
              }

              if (doPrint){
                count++;
                switch (attr) {
                  case 1:  // SELECT key
                    fprintf(stdout, "%d\n", key);
                    break;
                  case 2:  // SELECT value
                    fprintf(stdout, "%s\n", value.c_str());
                    break;
                  case 3:  // SELECT *
                    fprintf(stdout, "%d '%s'\n", key, value.c_str());
                    break;
                }
              }
            } else {
              goto exit_select;
            }
        } 
      }
    }
  } else {
    // scan the table file from the beginning
    rid.pid = rid.sid = 0;
    while (rid < rf.endRid()) {

      // read the tuple
      if ((rc = rf.read(rid, key, value)) < 0) {
        fprintf(stderr, "Error: while reading a tuple from table %s\n", table.c_str());
        goto exit_select;
      }

      // check the conditions on the tuple
      for (unsigned i = 0; i < cond.size(); i++) {
        // compute the difference between the tuple value and the condition value
        switch (cond[i].attr) {
          case 1:
          	diff = key - atoi(cond[i].value);
          	break;
          case 2:
          	diff = strcmp(value.c_str(), cond[i].value);
          	break;
        }

        // skip the tuple if any condition is not met
        switch (cond[i].comp) {
          case SelCond::EQ:
          	if (diff != 0) goto next_tuple;
          	break;
          case SelCond::NE:
          	if (diff == 0) goto next_tuple;
          	break;
          case SelCond::GT:
          	if (diff <= 0) goto next_tuple;
          	break;
          case SelCond::LT:
          	if (diff >= 0) goto next_tuple;
          	break;
          case SelCond::GE:
          	if (diff < 0) goto next_tuple;
          	break;
          case SelCond::LE:
          	if (diff > 0) goto next_tuple;
          	break;
        }
      }

      // the condition is met for the tuple. 
      // increase matching tuple counter
      count++;

      // print the tuple 
      switch (attr) {
        case 1:  // SELECT key
          fprintf(stdout, "%d\n", key);
          break;
        case 2:  // SELECT value
          fprintf(stdout, "%s\n", value.c_str());
          break;
        case 3:  // SELECT *
          fprintf(stdout, "%d '%s'\n", key, value.c_str());
          break;
      }

      // move to the next tuple
      next_tuple:
        ++rid;
    }

  }

















  // print matching tuple count if "select count(*)"
  if (attr == 4) {
    fprintf(stdout, "%d\n", count);
  }
  rc = 0;


  // close the table file and return
  exit_select:
    rf.close();
    if (indexExists) {
      idx.close();
    }
    return rc;
  
}

RC SqlEngine::load(const string& table, const string& loadfile, bool index)
{
  RecordFile rf;   // RecordFile containing the table
  RecordId   rid;  // record cursor

  //create recordfile or open existing
  if (rf.open(table + ".tbl", 'w') != 0) {
    fprintf(stderr, "Error could not open table %s.tbl!\n", table.c_str());
    return -1;
  }

  //open data file
  ifstream dataFile(loadfile.c_str());
  if (!dataFile.is_open()) { 
    fprintf(stderr, "Error could not open data file %s!\n", loadfile.c_str());
    return -2;
  }

  //read data file and append tuples
  int    key;     
  string value;
  string line;
  int split;

  BTreeIndex idx = BTreeIndex();
  if (index) {
    idx.open(table + ".idx",'w');
  }

  while(dataFile.good()) {
    getline(dataFile,line);
    if (line.length() > 1) {
      //split line into key and value
      split = line.find_first_of(',');
      key = atoi(line.substr(0,split).c_str());
      value = line.substr(split+1);

      //remove " marks
      value.erase(0,1);
      value.erase(value.length()-1,1);

      // append new record to record file
      rf.append(key,value,rid);
      if (index) {
        idx.insert(key,rid);
      } 
    }
  }

  if (index) {
    //idx.printIndex();
    idx.close();
  }

  //clean up 
  dataFile.close();
  rf.close();


  return 0;
}

RC SqlEngine::parseLoadLine(const string& line, int& key, string& value)
{
    const char *s;
    char        c;
    string::size_type loc;
    
    // ignore beginning white spaces
    c = *(s = line.c_str());
    while (c == ' ' || c == '\t') { c = *++s; }

    // get the integer key value
    key = atoi(s);

    // look for comma
    s = strchr(s, ',');
    if (s == NULL) { return RC_INVALID_FILE_FORMAT; }

    // ignore white spaces
    do { c = *++s; } while (c == ' ' || c == '\t');
    
    // if there is nothing left, set the value to empty string
    if (c == 0) { 
        value.erase();
        return 0;
    }

    // is the value field delimited by ' or "?
    if (c == '\'' || c == '"') {
        s++;
    } else {
        c = '\n';
    }

    // get the value string
    value.assign(s);
    loc = value.find(c, 0);
    if (loc != string::npos) { value.erase(loc); }

    return 0;
}
